import * as Box2D from "box2d.ts";
export class myBox2D {
  canvas: HTMLCanvasElement;
  ctx: CanvasRenderingContext2D;

  world: Box2D.b2World | null = null;
  floor: Rect | null = null;
  rect: Rect | null = null;
  circular: Circular | null = null;
  polygon: Polygon | null = null;
  complex: Complex | null = null;
  joint: RevoluteJoint | null = null;
  special: Circular | null = null;

  animateFrame: number = 0;
  test: number = 0;

  constructor(option: { canvas: HTMLCanvasElement; ctx: CanvasRenderingContext2D }) {
    this.canvas = option.canvas;
    this.ctx = option.ctx;
  }

  init() {
    this.world = new Box2D.b2World(new Box2D.b2Vec2(0, 9.8));
    this.world.m_allowSleep = true;

    this.floor = new Rect({
      world: this.world,
      position: { x: 0, y: 450 },
      density: 1.0,
      friction: 0.5,
      restitution: 0.2,
      size: { x: 640, y: 20 },
      type: Box2D.b2BodyType.b2_staticBody,
      userdata: { name: "polygon" },
    });

    this.rect = new Rect({
      world: this.world,
      position: { x: 40, y: 100 },
      density: 1.0,
      friction: 0.5,
      restitution: 0.3,
      size: { x: 30, y: 50 },
      type: Box2D.b2BodyType.b2_dynamicBody,
      userdata: { name: "polygon" },
    });

    this.circular = new Circular({
      world: this.world,
      position: { x: 130, y: 100 },
      density: 1.0,
      friction: 0.5,
      restitution: 0.3,
      radius: 30,
      type: Box2D.b2BodyType.b2_dynamicBody,
      userdata: { name: "circular" },
    });

    this.polygon = new Polygon({
      world: this.world,
      position: { x: 230, y: 50 },
      density: 1.0,
      friction: 0.5,
      restitution: 0.3,
      points: [new Box2D.b2Vec2(0, 0), new Box2D.b2Vec2(40, 50), new Box2D.b2Vec2(50, 100), new Box2D.b2Vec2(-50, 100), new Box2D.b2Vec2(-40, 50)],
      type: Box2D.b2BodyType.b2_dynamicBody,
      userdata: { name: "polygon" },
    });

    this.complex = new Complex({
      world: this.world,
      position: { x: 350, y: 50 },
      density: 1.0,
      friction: 0.5,
      restitution: 0.3,
      shapes: [new Box2D.b2CircleShape(30), new Box2D.b2PolygonShape().SetAsArray([new Box2D.b2Vec2(0, 0), new Box2D.b2Vec2(40, 50), new Box2D.b2Vec2(50, 100), new Box2D.b2Vec2(-50, 100), new Box2D.b2Vec2(-40, 50)])],
      type: Box2D.b2BodyType.b2_dynamicBody,
      userdata: { name: "complex" },
    });

    this.joint = new RevoluteJoint({
      body1: new Rect({
        world: this.world,
        position: { x: 480, y: 50 },
        density: 1.0,
        friction: 0.5,
        restitution: 0.5,
        size: { x: 50, y: 10 },
        type: Box2D.b2BodyType.b2_dynamicBody,
        userdata: { name: "polygon" },
      }),
      body2: new Polygon({
        world: this.world,
        position: { x: 470, y: 50 },
        density: 1.0,
        friction: 0.5,
        restitution: 0.5,
        points: [new Box2D.b2Vec2(0, 0), new Box2D.b2Vec2(40, 50), new Box2D.b2Vec2(50, 100), new Box2D.b2Vec2(-50, 100), new Box2D.b2Vec2(-40, 50)],
        type: Box2D.b2BodyType.b2_dynamicBody,
        userdata: { name: "polygon" },
      }),
      center: new Box2D.b2Vec2(470, 50),
      world: this.world,
    });

    this.special = new Circular({
      world: this.world,
      position: { x: 450, y: 0 },
      density: 1.0,
      friction: 0.5,
      restitution: 0.5,
      radius: 30,
      type: Box2D.b2BodyType.b2_dynamicBody,
      userdata: { name: "circular", life: 250 },
    });

    this.special.drawDiy = () => {
      let position = this.special?.body.GetPosition();
      let angle = this.special?.body.GetAngle();
      this.ctx.save();
      this.ctx.translate(position!.x, position!.y);
      this.ctx.rotate(angle!);
      this.ctx.fillStyle = "rgb(200,150,250)";
      this.ctx.beginPath();
      this.ctx.arc(0, 0, 30, 0, 2 * Math.PI);
      this.ctx.fill();

      this.ctx.fillStyle = "#000000";
      this.ctx.fillRect(-15, -15, 10, 5);
      this.ctx.fillRect(5, -15, 10, 5);

      this.ctx.strokeStyle = "#000000";
      this.ctx.beginPath();
      if (this.special?.body.GetUserData().life > 100) {
        this.ctx.arc(0, 0, 10, Math.PI, 2 * Math.PI,true);
      } else {
        this.ctx.arc(0, 10, 10, Math.PI, 2 * Math.PI,false);
      }
      this.ctx.stroke();
      this.ctx.restore();
    };

    let listener = new Box2D.b2ContactListener();
    listener.PostSolve = (contact, impulse) => {
      let body1 = contact.GetFixtureA().GetBody();
      let body2 = contact.GetFixtureB().GetBody();

      if (body1 == this.special?.body || body2 == this.special?.body) {
        let impulseAlongNarmal = impulse.normalImpulses[0];

        // console.log(impulse.normalImpulses)
        this.special.body.GetUserData().life -= impulseAlongNarmal / 3000;
        // console.log("碰撞触发 减少life",this.special.body.GetUserData().life)
      }
    };

    this.world.SetContactListener(listener);

    // this.floor.render(this.ctx)

    this.animate();
  }

  animate = () => {
    this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
    this.animateFrame = requestAnimationFrame(this.animate);

    this.world?.Step(1 / 60, 8, 3);
    let node = this.world?.GetBodyList();
    while (node) {
      let b = node;
      node = node.GetNext();
      let fixtrue = b?.GetFixtureList();
      let userdata = b.GetUserData();

      if (this.special) {
        this.special.drawDiy()
      }

      if (userdata.life <= 0) {
        this.world?.DestroyBody(b);
      }

      if (userdata.name == "polygon") {
        this.renderPolygon(this.ctx, b!.GetPosition(), b!.GetAngle(), fixtrue!);
      }
      if (userdata.name == "circular") {
        this.renderCircular(this.ctx, b!.GetPosition(), b!.GetAngle(), fixtrue!);
      }
      if (userdata.name == "complex") {
        while (fixtrue) {
          let f = fixtrue;
          fixtrue = fixtrue.GetNext();
          this.renderComplex(this.ctx, b!.GetPosition(), b!.GetAngle(), f!);
        }
      }
    }

    // this.test++;
    // if (this.test > 20) {
    //   cancelAnimationFrame(this.animateFrame);
    // }
  };

  renderComplex(ctx: CanvasRenderingContext2D, position: Box2D.b2Vec2, angle: number, f: Box2D.b2Fixture) {
    ctx.save();
    ctx.beginPath();
    ctx.translate(position.x, position.y);
    ctx.rotate(angle);
    if (f.GetShape().m_radius) {
      let radius: number = f.GetShape().m_radius;
      ctx.arc(0, 0, radius, 0, 2 * Math.PI);
    }
    if (f.GetShape().m_vertices) {
      let points: Box2D.b2Vec2[] = f.GetShape().m_vertices;
      points.forEach((point, index) => {
        if (index == 0) {
          ctx.moveTo(point.x, point.y);
        } else {
          ctx.lineTo(point.x, point.y);
        }
      });
    }
    ctx.closePath();
    ctx.stroke();
    ctx.restore();
  }

  renderCircular(ctx: CanvasRenderingContext2D, position: Box2D.b2Vec2, angle: number, f: Box2D.b2Fixture) {
    ctx.save();
    ctx.beginPath();
    ctx.translate(position.x, position.y);
    ctx.rotate(angle);
    let radius: number = f.GetShape().m_radius;
    ctx.arc(0, 0, radius, 0, 2 * Math.PI);
    ctx.closePath();
    ctx.stroke();
    ctx.restore();
  }

  renderPolygon(ctx: CanvasRenderingContext2D, position: Box2D.b2Vec2, angle: number, f: Box2D.b2Fixture) {
    ctx.save();
    ctx.beginPath();
    ctx.translate(position.x, position.y);
    ctx.rotate(angle);
    let points: Box2D.b2Vec2[] = f.GetShape().m_vertices;
    points.forEach((point, index) => {
      if (index == 0) {
        ctx.moveTo(point.x, point.y);
      } else {
        ctx.lineTo(point.x, point.y);
      }
    });
    ctx.closePath();
    ctx.stroke();
    ctx.restore();
  }
}

class RevoluteJoint {
  body1: Rect | Circular | Polygon | Complex;
  body2: Rect | Circular | Polygon | Complex;
  constructor(option: { body1: Rect | Circular | Polygon | Complex; body2: Rect | Circular | Polygon | Complex; center: Box2D.b2Vec2; world: Box2D.b2World }) {
    this.body1 = option.body1;
    this.body2 = option.body2;

    let jointDef = new Box2D.b2RevoluteJointDef();
    let jointCenter = option.center;
    jointDef.Initialize(this.body1.body, this.body2.body, jointCenter);
    option.world.CreateJoint(jointDef);
  }
}

class Complex {
  body: Box2D.b2Body;
  bodyDef: Box2D.b2BodyDef;
  fixture: Box2D.b2FixtureDef;

  constructor(option: { world: Box2D.b2World; position: { x: number; y: number }; density: number; friction: number; restitution: number; shapes: any[]; type: number; userdata: { name: string } }) {
    this.bodyDef = new Box2D.b2BodyDef();
    this.bodyDef.type = option.type;
    this.bodyDef.position.x = option.position.x;
    this.bodyDef.position.y = option.position.y;
    this.body = option.world.CreateBody(this.bodyDef);
    this.body.SetUserData(option.userdata);

    this.fixture = new Box2D.b2FixtureDef();
    this.fixture.density = option.density;
    this.fixture.friction = option.friction;
    this.fixture.restitution = option.restitution;

    option.shapes.forEach((shape) => {
      this.fixture.shape = shape;
      this.body.CreateFixture(this.fixture);
    });
  }
}

class Polygon {
  body: Box2D.b2Body;
  bodyDef: Box2D.b2BodyDef;
  fixture: Box2D.b2FixtureDef;

  constructor(option: { world: Box2D.b2World; position: { x: number; y: number }; density: number; friction: number; restitution: number; points: { x: number; y: number }[]; type: number; userdata: { name: string } }) {
    this.bodyDef = new Box2D.b2BodyDef();
    this.bodyDef.type = option.type;
    this.bodyDef.position.x = option.position.x;
    this.bodyDef.position.y = option.position.y;
    this.body = option.world.CreateBody(this.bodyDef);
    this.body.SetUserData(option.userdata);

    this.fixture = new Box2D.b2FixtureDef();
    this.fixture.density = option.density;
    this.fixture.friction = option.friction;
    this.fixture.restitution = option.restitution;
    this.fixture.shape = new Box2D.b2PolygonShape().SetAsArray(option.points);
    this.body.CreateFixture(this.fixture);
  }
}

class Circular {
  body: Box2D.b2Body;
  bodyDef: Box2D.b2BodyDef;
  fixture: Box2D.b2FixtureDef;
  drawDiy(): void {}

  constructor(option: { world: Box2D.b2World; position: { x: number; y: number }; density: number; friction: number; restitution: number; radius: number; type: number; userdata: any }) {
    this.bodyDef = new Box2D.b2BodyDef();
    this.bodyDef.type = option.type;
    this.bodyDef.position.x = option.position.x;
    this.bodyDef.position.y = option.position.y;
    this.body = option.world.CreateBody(this.bodyDef);
    this.body.SetUserData(option.userdata);

    this.fixture = new Box2D.b2FixtureDef();
    this.fixture.density = option.density;
    this.fixture.friction = option.friction;
    this.fixture.restitution = option.restitution;
    this.fixture.shape = new Box2D.b2CircleShape(option.radius);
    this.body.CreateFixture(this.fixture);
  }
}

class Rect {
  body: Box2D.b2Body;
  bodyDef: Box2D.b2BodyDef;
  fixture: Box2D.b2FixtureDef;

  constructor(option: { world: Box2D.b2World; position: { x: number; y: number }; density: number; friction: number; restitution: number; size: { x: number; y: number }; type: number; userdata: { name: string } }) {
    this.bodyDef = new Box2D.b2BodyDef();
    this.bodyDef.type = option.type;
    this.bodyDef.position.x = option.position.x;
    this.bodyDef.position.y = option.position.y;
    this.body = option.world.CreateBody(this.bodyDef);
    this.body.SetUserData(option.userdata);

    this.fixture = new Box2D.b2FixtureDef();
    this.fixture.density = option.density;
    this.fixture.friction = option.friction;
    this.fixture.restitution = option.restitution;
    this.fixture.shape = new Box2D.b2PolygonShape().SetAsBox(option.size.x, option.size.y);
    this.body.CreateFixture(this.fixture);
  }
}
